/*
 * Copyright 2011-2012 by The Regents of the University of California Licensed
 * under the Apache License, Version 2.0 (the "License"); you may not use this
 * file except in compliance with the License. you may obtain a copy of the
 * License from
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package edu.ucsd.db.bassarisk.query.expression;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import edu.ucsd.db.bassarisk.exception.VisitorException;
import edu.ucsd.db.bassarisk.query.QueryExpression;
import edu.ucsd.db.bassarisk.query.TransformReturn;
import edu.ucsd.db.bassarisk.query.ast.SyntaxExpressions.SyntaxExpression;
import edu.ucsd.db.bassarisk.query.binding.Bindings.Binding;
import edu.ucsd.db.bassarisk.query.expression.visitor.ExpressionTreeVisitor;
import edu.ucsd.db.bassarisk.schema.RunClass;

public abstract class Expressions {
  public static class BooleanAnd extends BooleanExpr {
    private final ArrayList<Expression> subExprs;

    public BooleanAnd(final SyntaxExpression innerExpression,
                      final List<Expression> subExprs) {
      super(innerExpression);
      this.subExprs = new ArrayList<Expression>();
      this.subExprs.addAll(subExprs);
    }

    @Override
    public <R, A> R accept(final ExpressionTreeVisitor<R, A> visitor, final A arg)
        throws VisitorException {
      return visitor.visitBooleanAnd(this, arg);
    }

    @Override
    public String friendlyName() {
      return "boolean and";
    }

    @Override
    public String functionName() {
      return "And";
    }

    @SuppressWarnings("unchecked")
    @Override
    public List<Expression> getSubExprs() {
      return (List<Expression>) this.subExprs.clone();
    }

    @Override
    public Expression transform(final List<Expression> newSubExprs) {
      return new BooleanAnd(this.getInnerExpression(), newSubExprs);
    }
  }

  public abstract static class BooleanExpr extends NonterminalExpr {
    private BooleanExpr(final SyntaxExpression innerExpression) {
      super(innerExpression);
    }

    @Override
    public <R, A> R accept(final ExpressionTreeVisitor<R, A> visitor, final A arg)
        throws VisitorException {
      return visitor.visitBooleanExpr(this, arg);
    }
  }

  public static class BooleanNot extends BooleanExpr implements IUnaryExpression {
    private final Expression subExpr;

    public BooleanNot(final SyntaxExpression innerExpression,
                      final Expression subExpression) {
      super(innerExpression);
      this.subExpr = subExpression;
    }

    @Override
    public <R, A> R accept(final ExpressionTreeVisitor<R, A> visitor, final A arg)
        throws VisitorException {
      return visitor.visitBooleanNot(this, arg);
    }

    @Override
    public String friendlyName() {
      return "boolean not";
    }

    @Override
    public String functionName() {
      return "Not";
    }

    @Override
    public Expression getSubExpr() {
      return this.subExpr;
    }

    @Override
    public List<Expression> getSubExprs() {
      return Expressions.list(this.subExpr);
    }

    @Override
    public Expression transform(final List<Expression> newSubExprs) {
      assert newSubExprs.size() == 1;
      return new BooleanNot(this.getInnerExpression(), newSubExprs.get(0));
    }
  }

  public static class BooleanOr extends BooleanExpr {
    private final Map<String, Binding[]> establishedBindingProducts;
    private final Map<String, Binding> establishedBindingUnions;
    private final ArrayList<Expression> subExprs;

    public BooleanOr(final SyntaxExpression innerExpression,
                     final List<Expression> subExprs) {
      super(innerExpression);
      this.subExprs = new ArrayList<Expression>();
      this.subExprs.addAll(subExprs);
      this.establishedBindingProducts = new HashMap<String, Binding[]>();
      this.establishedBindingUnions = new HashMap<String, Binding>();
    }

    @Override
    public <R, A> R accept(final ExpressionTreeVisitor<R, A> visitor, final A arg)
        throws VisitorException {
      return visitor.visitBooleanOr(this, arg);
    }

    @Override
    public String friendlyName() {
      return "boolean or";
    }

    @Override
    public String functionName() {
      return "Or";
    }

    public Iterable<Entry<String, Binding[]>> getEstablishedBindingProducts() {
      return this.establishedBindingProducts.entrySet();
    }

    public Iterable<Entry<String, Binding>> getEstablishedBindingUnions() {
      return this.establishedBindingUnions.entrySet();
    }

    @SuppressWarnings("unchecked")
    @Override
    public List<Expression> getSubExprs() {
      return (List<Expression>) this.subExprs.clone();
    }

    public void putEstablishedBindingProduct(final String runClassName,
                                             final Expression subExpr,
                                             final Binding binding) {
      if (this.establishedBindingProducts.get(runClassName) == null) {
        int subExprCount = this.getSubExprs().size();
        this.establishedBindingProducts.put(runClassName, new Binding[subExprCount]);
      }
      int subExprIndex = this.getSubExprs().indexOf(subExpr);
      this.establishedBindingProducts.get(runClassName)[subExprIndex] = binding;
    }

    public void putEstablishedBindingUnion(final String runClassName,
                                           final Binding binding) {
      this.establishedBindingUnions.put(runClassName, binding);
    }

    @Override
    public Expression transform(final List<Expression> newSubExprs) {
      BooleanOr newOr = new BooleanOr(this.getInnerExpression(), newSubExprs);
      newOr.establishedBindingProducts.putAll(this.establishedBindingProducts);
      newOr.establishedBindingUnions.putAll(this.establishedBindingUnions);
      return newOr;
    }
  }

  public abstract static class Expression implements QueryExpression,
      TransformReturn<Expression> {
    private final SyntaxExpression innerExpression;

    private Expression(final SyntaxExpression innerExpression) {
      this.innerExpression = innerExpression;
    }

    public abstract <R, A> R accept(ExpressionTreeVisitor<R, A> visitor, A arg)
        throws VisitorException;

    @SuppressWarnings("unchecked")
    @Override
    public <V, R, A> R accept(final V visitor, final A args) throws VisitorException {
      if (visitor instanceof ExpressionTreeVisitor<?, ?>) {
        return this.accept((ExpressionTreeVisitor<R, A>) visitor, args);
      } else {
        throw VisitorException.invalidVisitorType();
      }
    }

    public abstract String friendlyName();

    public SyntaxExpression getInnerExpression() {
      return this.innerExpression;
    }

    @Override
    public final String getName() {
      return this.friendlyName();
    }

    @Override
    public final int getStartColumn() {
      return this.innerExpression.getStartColumn();
    }

    @Override
    public final int getStartLine() {
      return this.innerExpression.getStartLine();
    }

    @Override
    public Expression getTransformationRoot() {
      return this;
    }
  }

  public static interface IBindingExpression {
    public Binding getEstablishedBinding();

    public void setEstablishedBinding(Binding establishedBinding);
  }

  public static interface IDistaceConstrainingExpression {
    int getDistanceConstraint();
  }

  public static interface IStructureConstrainingExpression {
    RunClass getRunClass();

    String getRunClassName();
  }

  public static interface IUnaryExpression {
    Expression getSubExpr();
  }

  public static class KeywordMatchExpr extends Expression implements
      IBindingExpression {
    private Binding establishedBinding;
    private final boolean isReferenced;
    private final String reference;
    private final String token;

    public KeywordMatchExpr(final SyntaxExpression innerExpression,
                            final String token, final boolean isReferenced,
                            final String reference) {
      super(innerExpression);
      this.token = token;
      this.isReferenced = isReferenced;
      this.reference = reference;
    }

    @Override
    public <R, A> R accept(final ExpressionTreeVisitor<R, A> visitor, final A arg)
        throws VisitorException {
      return visitor.visitKeywordMatchExpr(this, arg);
    }

    @Override
    public String friendlyName() {
      return "keyword reference";
    }

    @Override
    public Binding getEstablishedBinding() {
      return this.establishedBinding;
    }

    public String getReference() {
      return this.reference;
    }

    public String getToken() {
      return this.token;
    }

    public boolean isReferenced() {
      return this.isReferenced;
    }

    @Override
    public void setEstablishedBinding(final Binding establishedBinding) {
      this.establishedBinding = establishedBinding;
    }
  }

  public abstract static class NonterminalExpr extends Expression {
    private NonterminalExpr(final SyntaxExpression innerExpression) {
      super(innerExpression);
    }

    public abstract String functionName();

    public abstract List<Expression> getSubExprs();

    public abstract Expression transform(List<Expression> newSubExprs);
  }

  public static class PositionMaxSeparation extends PositionPredExpr implements
      IStructureConstrainingExpression, IDistaceConstrainingExpression {
    private final int distanceConstraint;
    private final RunClass runClass;

    private PositionMaxSeparation(final PositionMaxSeparation old,
                                  final Expression subExpr) {
      super(old, subExpr);
      this.distanceConstraint = old.distanceConstraint;
      this.runClass = old.runClass;
    }

    public PositionMaxSeparation(final SyntaxExpression innerExpression,
                                 final Expression subExpr,
                                 final int distanceConstraint,
                                 final RunClass runClass) {
      super(innerExpression, subExpr);
      this.distanceConstraint = distanceConstraint;
      this.runClass = runClass;
    }

    @Override
    public <R, A> R accept(final ExpressionTreeVisitor<R, A> visitor, final A arg)
        throws VisitorException {
      return visitor.visitPositionMaxSeparation(this, arg);
    }

    @Override
    public String friendlyName() {
      return "postional maximum separation";
    }

    @Override
    public String functionName() {
      return "MaxSeparation";
    }

    @Override
    public int getDistanceConstraint() {
      return this.distanceConstraint;
    }

    @Override
    public RunClass getRunClass() {
      return this.runClass;
    }

    @Override
    public String getRunClassName() {
      return this.runClass.getName();
    }

    @Override
    public Expression transform(final Expression newSubExpr) {
      return new PositionMaxSeparation(this, newSubExpr);
    }
  }

  public static class PositionMinSeparation extends PositionPredExpr implements
      IStructureConstrainingExpression, IDistaceConstrainingExpression {
    private final int distanceConstraint;
    private final RunClass runClass;

    private PositionMinSeparation(final PositionMinSeparation old,
                                  final Expression subExpr) {
      super(old, subExpr);
      this.distanceConstraint = old.distanceConstraint;
      this.runClass = old.runClass;
    }

    public PositionMinSeparation(final SyntaxExpression innerExpression,
                                 final Expression subExpr,
                                 final int distanceConstraint,
                                 final RunClass runClass) {
      super(innerExpression, subExpr);
      this.distanceConstraint = distanceConstraint;
      this.runClass = runClass;
    }

    @Override
    public <R, A> R accept(final ExpressionTreeVisitor<R, A> visitor, final A arg)
        throws VisitorException {
      return visitor.visitPositionMinSeparation(this, arg);
    }

    @Override
    public String friendlyName() {
      return "positional minimum separation";
    }

    @Override
    public String functionName() {
      return "MinSeparation";
    }

    @Override
    public int getDistanceConstraint() {
      return this.distanceConstraint;
    }

    @Override
    public RunClass getRunClass() {
      return this.runClass;
    }

    @Override
    public String getRunClassName() {
      return this.runClass.getName();
    }

    @Override
    public Expression transform(final Expression newSubExpr) {
      return new PositionMinSeparation(this, newSubExpr);
    }
  }

  public static class PositionOrdered extends PositionPredExpr {
    private PositionOrdered(final PositionOrdered old, final Expression subExpr) {
      super(old, subExpr);
    }

    public PositionOrdered(final SyntaxExpression innerExpression,
                           final Expression subExpr) {
      super(innerExpression, subExpr);
    }

    @Override
    public <R, A> R accept(final ExpressionTreeVisitor<R, A> visitor, final A arg)
        throws VisitorException {
      return visitor.visitPositionOrdered(this, arg);
    }

    @Override
    public String friendlyName() {
      return "positional ordered";
    }

    @Override
    public String functionName() {
      return "Ordered";
    }

    @Override
    public Expression transform(final Expression newSubExpr) {
      return new PositionOrdered(this, newSubExpr);
    }
  }

  public static class PositionPhrase extends PositionPredExpr {
    private PositionPhrase(final PositionPhrase old, final Expression subExpr) {
      super(old, subExpr);
    }

    public PositionPhrase(final SyntaxExpression innerExpression,
                          final Expression subExpr) {
      super(innerExpression, subExpr);
    }

    @Override
    public <R, A> R accept(final ExpressionTreeVisitor<R, A> visitor, final A arg)
        throws VisitorException {
      return visitor.visitPositionPhrase(this, arg);
    }

    @Override
    public String friendlyName() {
      return "positional phrase";
    }

    @Override
    public String functionName() {
      return "Phrase";
    }

    @Override
    public Expression transform(final Expression newSubExpr) {
      return new PositionPhrase(this, newSubExpr);
    }
  }

  public abstract static class PositionPredExpr extends UnaryNonterminalExpr {
    private final List<Binding> topLevelBindings;

    private PositionPredExpr(final PositionPredExpr oldExpr,
                             final Expression subExpression) {
      super(oldExpr.getInnerExpression(), subExpression);
      this.topLevelBindings = oldExpr.topLevelBindings;
    }

    private PositionPredExpr(final SyntaxExpression innerExpression,
                             final Expression subExpression) {
      super(innerExpression, subExpression);
      this.topLevelBindings = new ArrayList<Binding>();
    }

    @Override
    public <R, A> R accept(final ExpressionTreeVisitor<R, A> visitor, final A arg)
        throws VisitorException {
      return visitor.visitPositionPredExpr(this, arg);
    }

    public void addTopLevelBinding(final Binding binding) {
      this.topLevelBindings.add(binding);
    }

    public Iterable<Binding> topLevelBindings() {
      return this.topLevelBindings;
    }
  }

  public static class PositionSeparation extends PositionPredExpr implements
      IStructureConstrainingExpression, IDistaceConstrainingExpression {
    private final int distanceConstraint;
    private final RunClass runClass;

    private PositionSeparation(final PositionSeparation old, final Expression subExpr) {
      super(old, subExpr);
      this.distanceConstraint = old.distanceConstraint;
      this.runClass = old.runClass;
    }

    public PositionSeparation(final SyntaxExpression innerExpression,
                              final Expression subExpr,
                              final int distanceConstraint, final RunClass runClass) {
      super(innerExpression, subExpr);
      this.distanceConstraint = distanceConstraint;
      this.runClass = runClass;
    }

    @Override
    public <R, A> R accept(final ExpressionTreeVisitor<R, A> visitor, final A arg)
        throws VisitorException {
      return visitor.visitPositionSeparation(this, arg);
    }

    @Override
    public String friendlyName() {
      return "positional exact separation";
    }

    @Override
    public String functionName() {
      return "Separation";
    }

    @Override
    public int getDistanceConstraint() {
      return this.distanceConstraint;
    }

    @Override
    public RunClass getRunClass() {
      return this.runClass;
    }

    @Override
    public String getRunClassName() {
      return this.runClass.getName();
    }

    @Override
    public Expression transform(final Expression newSubExpr) {
      return new PositionSeparation(this, newSubExpr);
    }
  }

  public static class PositionWindow extends PositionPredExpr implements
      IStructureConstrainingExpression, IDistaceConstrainingExpression {
    private final int distanceConstraint;
    private final RunClass runClass;

    private PositionWindow(final PositionWindow old, final Expression subExpr) {
      super(old, subExpr);
      this.distanceConstraint = old.distanceConstraint;
      this.runClass = old.runClass;
    }

    public PositionWindow(final SyntaxExpression innerExpression,
                          final Expression subExpr, final int distanceConstraint,
                          final RunClass runClass) {
      super(innerExpression, subExpr);
      this.distanceConstraint = distanceConstraint;
      this.runClass = runClass;
    }

    @Override
    public <R, A> R accept(final ExpressionTreeVisitor<R, A> visitor, final A arg)
        throws VisitorException {
      return visitor.visitPositionWindow(this, arg);
    }

    @Override
    public String friendlyName() {
      return "positional window";
    }

    @Override
    public String functionName() {
      return "Window";
    }

    @Override
    public int getDistanceConstraint() {
      return this.distanceConstraint;
    }

    @Override
    public RunClass getRunClass() {
      return this.runClass;
    }

    @Override
    public String getRunClassName() {
      return this.runClass.getName();
    }

    @Override
    public Expression transform(final Expression newSubExprs) {
      return new PositionWindow(this, newSubExprs);
    }
  }

  public static class StructureFind extends StructurePredExpr implements
      IBindingExpression {
    private Binding establishedBinding;

    public StructureFind(final SyntaxExpression innerExpression,
                         final Expression subExpr, final RunClass runClass) {
      super(innerExpression, subExpr, runClass);
    }

    @Override
    public <R, A> R accept(final ExpressionTreeVisitor<R, A> visitor, final A arg)
        throws VisitorException {
      return visitor.visitStructureFind(this, arg);
    }

    @Override
    public String friendlyName() {
      return "structural find";
    }

    @Override
    public String functionName() {
      return "FIND";
    }

    @Override
    public Binding getEstablishedBinding() {
      return this.establishedBinding;
    }

    @Override
    public RunClass getRunClass() {
      return this.runClass;
    }

    @Override
    public void setEstablishedBinding(final Binding establishedBinding) {
      this.establishedBinding = establishedBinding;
    }

    @Override
    public Expression transform(final Expression newSubExpr) {
      StructureFind newFind =
          new StructureFind(this.getInnerExpression(), newSubExpr, this.runClass);
      newFind.establishedBinding = this.establishedBinding;
      return newFind;
    }
  }

  public static class StructureIn extends StructurePredExpr {
    public StructureIn(final SyntaxExpression innerExpression,
                       final Expression subExpr, final RunClass runClass) {
      super(innerExpression, subExpr, runClass);
    }

    @Override
    public <R, A> R accept(final ExpressionTreeVisitor<R, A> visitor, final A arg)
        throws VisitorException {
      return visitor.visitStructureIn(this, arg);
    }

    @Override
    public String friendlyName() {
      return "structural in";
    }

    @Override
    public String functionName() {
      return "In";
    }

    @Override
    public RunClass getRunClass() {
      return this.runClass;
    }

    @Override
    public Expression transform(final Expression newSubExpr) {
      return new StructureIn(this.getInnerExpression(), newSubExpr, this.runClass);
    }
  }

  public abstract static class StructurePredExpr extends UnaryNonterminalExpr
      implements IStructureConstrainingExpression {
    protected final RunClass runClass;

    private StructurePredExpr(final SyntaxExpression innerExpression,
                              final Expression subExpr, final RunClass runClass) {
      super(innerExpression, subExpr);
      this.runClass = runClass;
    }

    @Override
    public <R, A> R accept(final ExpressionTreeVisitor<R, A> visitor, final A arg)
        throws VisitorException {
      return visitor.visitStructurePredExpr(this, arg);
    }

    @Override
    public String getRunClassName() {
      return this.runClass.getName();
    }
  }

  public static class StructureSame extends StructurePredExpr implements
      IBindingExpression {
    private Binding establishedBinding;

    public StructureSame(final SyntaxExpression innerExpression,
                         final Expression subExpr, final RunClass runClass) {
      super(innerExpression, subExpr, runClass);
    }

    @Override
    public <R, A> R accept(final ExpressionTreeVisitor<R, A> visitor, final A arg)
        throws VisitorException {
      return visitor.visitStructureSame(this, arg);
    }

    @Override
    public String friendlyName() {
      return "structural same";
    }

    @Override
    public String functionName() {
      return "Same";
    }

    @Override
    public Binding getEstablishedBinding() {
      return this.establishedBinding;
    }

    @Override
    public RunClass getRunClass() {
      return this.runClass;
    }

    @Override
    public void setEstablishedBinding(final Binding establishedBinding) {
      this.establishedBinding = establishedBinding;
    }

    @Override
    public Expression transform(final Expression newSubExpr) {
      StructureSame newSame =
          new StructureSame(this.getInnerExpression(), newSubExpr, this.runClass);
      newSame.establishedBinding = this.establishedBinding;
      return newSame;
    }
  }

  public abstract static class UnaryNonterminalExpr extends NonterminalExpr
      implements IUnaryExpression {
    protected final Expression subExpr;

    private UnaryNonterminalExpr(final SyntaxExpression innerExpression,
                                 final Expression subExpression) {
      super(innerExpression);
      this.subExpr = subExpression;
    }

    @Override
    public Expression getSubExpr() {
      return this.subExpr;
    }

    @Override
    public final List<Expression> getSubExprs() {
      return Expressions.list(this.subExpr);
    }

    public abstract Expression transform(Expression newSubExprs);

    @Override
    public final Expression transform(final List<Expression> newSubExprs) {
      assert newSubExprs.size() == 1;
      return this.transform(newSubExprs.get(0));
    }
  }

  public static List<Expression> list(final Expression... exprs) {
    ArrayList<Expression> list = new ArrayList<Expression>();
    for (Expression expr : exprs) {
      list.add(expr);
    }
    return list;
  }
}
